/**
 * Copyright (c) 2009 Teoti Graphix, LLC See:
 *     http://tkarchitecture.teotigraphix.org
 * 
 * Permission is hereby granted, free of charge, to any person
 * obtaining a copy of this software and associated documentation
 * files (the "Software"), to deal in the Software without
 * restriction, including without limitation the rights to use,
 * copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following
 * conditions:
 * 
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 * OTHER DEALINGS IN THE SOFTWARE.
 * 
 * @author Michael Schmalle mschmalle@teotigraphix.com
 */
package org.teotigraphix.dx.core
{

import flash.events.IEventDispatcher;
import flash.utils.getQualifiedClassName;

import mx.core.IFactory;

import org.teotigraphix.dx.event.ModelLayerEvent;
import org.teotigraphix.dx.interfaces.IActivate;
import org.teotigraphix.dx.interfaces.IBehaviorLayer;
import org.teotigraphix.dx.interfaces.IBehaviorLayerAware;
import org.teotigraphix.dx.interfaces.IComponentAware;
import org.teotigraphix.dx.interfaces.IComponentBehavior;
import org.teotigraphix.dx.interfaces.IComponentBehaviorCore;
import org.teotigraphix.dx.interfaces.IComponentModel;
import org.teotigraphix.dx.interfaces.IComponentModelCore;
import org.teotigraphix.dx.interfaces.IComponentSkin;
import org.teotigraphix.dx.interfaces.ICoreModelAware;
import org.teotigraphix.dx.interfaces.IModelLayer;
import org.teotigraphix.dx.interfaces.IModelLayerAware;
import org.teotigraphix.dx.interfaces.ISkinLayer;
import org.teotigraphix.dx.interfaces.ISkinLayerAware;
import org.teotigraphix.dx.interfaces.IToolKitComponent;
import org.teotigraphix.dx.interfaces.IToolKitFactory;
import org.teotigraphix.dx.interfaces.IToolKitFactoryAware;
import org.teotigraphix.dx.interfaces.IToolKitPhase;
import org.teotigraphix.dx.patterns.component.skin.SkinFactory;
import org.teotigraphix.dx.patterns.manager.ModelManager;
import org.teotigraphix.dx.patterns.manager.PhaseManager;

// TODO (mschmalle) create interface and implement like a manager singleton

/**
 * The dx framework Facade for commonly used methods
 * and other various functionality. 
 * 
 * @copyright Teoti Graphix, LLC
 * @author Michael Schmalle
 * @date 12-04-08, 07-07-09
 */
public class ToolKitHelper
{
	include "Version.as"
	
	//--------------------------------------------------------------------------
	//
	//  Public Class [phase] :: Methods
	//
	//--------------------------------------------------------------------------
	
	/**
	 * Wraps the <code>PhaseManager</code>'s <code>dispatchPhase()</code>
	 * method.
	 * 
	 * <p>This method allows an <code>IToolKitPhase</code> or string phase
	 * for the <code>phase</code> argument.</p>
	 * 
	 * @param client The client requesting the phase.
	 * @param phase The <code>IToolKitPhase</code> or string phase.
	 * @param data The data that will be assigned to the phase if the 
	 * <code>phase</code> argument is a string.
	 */
	public static function dispatchPhase(
		client:IToolKitComponent, phase:Object, data:Object = null):void
	{
		var o:IToolKitPhase;
		if (phase is String)
		{
			o = new ToolKitPhase(phase.toString(), data);
		}
		else if (phase is IToolKitPhase)
		{
			o = phase as IToolKitPhase;
		}
		
		if (!(o is IToolKitPhase))
			throw new Error("Invalidate toolkit phase", phase.toString());
		
		PhaseManager.dispatchPhase(client, o);
	}
	
	//--------------------------------------------------------------------------
	//
	//  Public Class [factory] :: Methods
	//
	//--------------------------------------------------------------------------
	
	/**
	 * Returns the <code>toolKitFactory</code> instance for the 
	 * <code>component</code>.
	 * 
	 * <p>The method will first check for the <code>toolKitFactory</code> 
	 * property then it will check the <code>toolKitFactoryClass</code> style for 
	 * a class that has a <code>getInstance()</code> method that returns the 
	 * factory instance.</p>
	 * 
	 * @param component The <code>IToolKitFactoryAware</code> component.
	 */
	public static function getToolKitFactory(
		instance:IToolKitComponent):IToolKitFactory
	{
		var factory:IToolKitFactory;
		var tkfclient:IToolKitFactoryAware = instance as IToolKitFactoryAware;
		if (tkfclient)
		{
			factory = tkfclient.toolKitFactory;
			if (!factory)
			{
				var clazz:Class = instance.getStyle("toolKitFactoryClass");
				if (clazz)
				{
					try {
						factory = clazz['getInstance']() as IToolKitFactory;
					}
					catch (e:TypeError)
					{
						trace(clazz + " has not implemented getInstance()");
					}
					catch (e:Error)
					{
						trace("ToolKitHelper.getToolKitFactory()", e);
					} 
				}
			}
		}
		return factory;
	}
	
	/**
	 * Facade property update of the <code>toolKitFactory</code>.
	 * 
	 * <p>Used by the <code>ToolKitManager</code>.</p>
	 * 
	 * @param component The component getting updated.
	 * @param factory The new <code>toolKitFactory</code>.
	 * @param oldFactory The old <code>toolKitFactory</code>.
	 */
	public static function setToolKitFactory(
		host:IToolKitComponent, 
		factory:IToolKitFactory, 
		oldFactory:IToolKitFactory):void
	{
		if (factory === oldFactory)
			return;
	}
	
	//--------------------------------------------------------------------------
	//
	//  Public Class [core] :: Methods
	//
	//--------------------------------------------------------------------------
	
	/**
	 * Facade property update of the <code>coreModel</code>.
	 * 
	 * <p>Used by the <code>ToolKitManager</code>.</p>
	 * 
	 * @param component The component getting updated.
	 * @param core The new <code>IComponentModelCore</code>.
	 * @param oldCore The old <code>IComponentModelCore</code>.
	 * 
	 * @event org.teotigraphix.dx.core.event.ModelLayerEvent.MODEL_REMOVE 
	 * Dispatched if <code>oldCore is IToolKitComponentAware</code>.
	 * @event org.teotigraphix.dx.core.event.ModelLayerEvent.MODEL_ADD 
	 * Dispatched if <code>core is IToolKitComponentAware</code>.
	 */
	public static function setCoreModel(
		component:IToolKitComponent, 
		core:IComponentModelCore, 
		oldCore:IComponentModelCore):Boolean
	{
		if (core === oldCore)
			return false;
		
		var e:ModelLayerEvent;
			
		if (oldCore is IComponentAware)
		{
			IComponentAware(oldCore).component = null;
			
			oldCore.deactivate(IEventDispatcher(component));
			
			e = new ModelLayerEvent(
				ModelLayerEvent.MODEL_REMOVE, IComponentModelCore, 
				oldCore, IEventDispatcher(component));
			
			component.dispatchEvent(e);
			
			ModelManager.deactivateCoreModel(
				ICoreModelAware(component), oldCore);
		}
		
		if (core is IComponentAware)
		{
			IComponentAware(core).component = IEventDispatcher(component);			
			
			core.activate(IEventDispatcher(component));
			
			e = new ModelLayerEvent(
				ModelLayerEvent.MODEL_ADD, IComponentModelCore, 
				core, IEventDispatcher(component));
			
			component.dispatchEvent(e);
			
			ModelManager.activateCoreModel(
				ICoreModelAware(component), core);
		}
		
		return true;
	}
	
	/**
	 * Facade property update of the <code>coreSkin</code>.
	 * 
	 * <p>Used by the <code>ToolKitManager</code>.</p>
	 * 
	 * @param component The component getting updated.
	 * @param core The new <code>Skin</code>.
	 * @param oldCore The old <code>Skin</code>.
	 */
	public static function setCoreSkin(
		component:IToolKitComponent, 
		core:IComponentSkin, 
		oldCore:IComponentSkin):Boolean
	{
		if (core === oldCore || core == null)
			return false;
// FIXME		
		var factory:IFactory = new SkinFactory(core);
		component.setStyle("skinFactory", factory);
		
		//if (oldCore is IToolKitComponentAware)
		//	IToolKitComponentAware(oldCore).component = null;
		
		//if (core is IToolKitComponentAware)
		//	IToolKitComponentAware(core).component = IToolKitComponent(component);			
		
		return true;
	}	
	
	/**
	 * Facade property update of the <code>coreBehavior</code>.
	 * 
	 * <p>Used by the <code>ToolKitManager</code>.</p>
	 * 
	 * @param component The component getting updated.
	 * @param core The new <code>IComponentBehaviorCore</code>.
	 * @param oldCore The old <code>IComponentBehaviorCore</code>.
	 */
	public static function setCoreBehavior(
		component:IToolKitComponent, 
		core:IComponentBehaviorCore, 
		oldCore:IComponentBehaviorCore):Boolean
	{
		if (core === oldCore)
			return false;
		
		if (oldCore is IComponentAware)
			IComponentAware(oldCore).component = null;
		
		if (core is IComponentAware)
			IComponentAware(core).component = IEventDispatcher(component);			
		
		return true;
	}
	
	//--------------------------------------------------------------------------
	//
	//  Public Class [layer] :: Methods
	//
	//--------------------------------------------------------------------------
	
	/**
	 * Facade property update of the <code>modelLayer</code>.
	 * 
	 * <p>Used by the <code>ToolKitManager</code>.</p>
	 * 
	 * @param component The component getting updated.
	 * @param layer The new <code>IModelLayer</code>.
	 * @param oldLayer The old <code>IModelLayer</code>.
	 */
	public static function setModelLayer(
		host:IModelLayerAware, 
		layer:IModelLayer, 
		oldLayer:IModelLayer):void
	{
		if (layer === oldLayer) 
			return;
		
		if (oldLayer)
			ModelManager.deactivate(host, oldLayer);
		
		if (layer)
			ModelManager.activate(host, layer);
	}
	
	/**
	 * Facade property update of the <code>models</code>.
	 * 
	 * <p>Used by the <code>ToolKitManager</code>.</p>
	 * 
	 * @param component The component getting updated.
	 * @param service The new <code>models</code>.
	 * @param oldServices The old <code>models</code>.
	 */
	public static function setModels(
		component:IModelLayerAware,
		models:Array, 
		oldModels:Array):void
	{
		var model:IComponentModel;
		var type:Class;
		
		if (oldModels)
		{
			for each (model in oldModels)
			{
				type = ToolKitUtil.getDefinition(getQualifiedClassName(model)) as Class;
				component.modelLayer.removeModel(type);
			}
		}
		
		if (models)
		{
			for each (model in models)
			{
				type = ToolKitUtil.getDefinition(getQualifiedClassName(model)) as Class;
				component.modelLayer.registerModel(type, model);
			}
		}
	}
	/**
	 * Facade property update of the <code>skinLayer</code>.
	 * 
	 * <p>Used by the <code>ToolKitManager</code>.</p>
	 * 
	 * @param component The component getting updated.
	 * @param layer The new <code>skinLayer</code>.
	 * @param oldLayer The old <code>skinLayer</code>.
	 */
	public static function setSkinLayer(
		host:ISkinLayerAware, 
		layer:ISkinLayer, 
		oldLayer:ISkinLayer):void
	{
		if (layer === oldLayer)
			return;
	}
	
	/**
	 * Facade property update of the <code>skins</code>.
	 * 
	 * <p>Used by the <code>ToolKitManager</code>.</p>
	 * 
	 * @param component The component getting updated.
	 * @param behaviors The new <code>skins</code>.
	 * @param oldBehaviors The old <code>skins</code>.
	 */
	public static function setSkins(
		component:ISkinLayerAware,
		skins:Array, 
		oldSkins:Array):void
	{
	}
	
	/**
	 * Facade property update of the <code>behaviorLayer</code>.
	 * 
	 * <p>Used by the <code>ToolKitManager</code>.</p>
	 * 
	 * @param component The component getting updated.
	 * @param layer The new <code>IBehaviorLayer</code>.
	 * @param oldLayer The old <code>IBehaviorLayer</code>.
	 */
	public static function setBehaviorLayer(
		component:IBehaviorLayerAware,
		layer:IBehaviorLayer, 
		oldLayer:IBehaviorLayer):Boolean
	{				
		if (layer === oldLayer) 
			return false;
	
		// remove all handlers and refs from the behavior pointing
		// this this Component
		if (oldLayer is IActivate)
			IActivate(oldLayer).deactivate(IEventDispatcher(component));
		
		if (layer is IActivate)
			IActivate(layer).activate(IEventDispatcher(component));
		
		return true;		
	}
	
	/**
	 * Facade property update of the <code>behaviors</code>.
	 * 
	 * <p>Used by the <code>ToolKitManager</code>.</p>
	 * 
	 * @param component The component getting updated.
	 * @param behaviors The new <code>behaviors</code>.
	 * @param oldBehaviors The old <code>behaviors</code>.
	 */
	public static function setBehaviors(
		component:IBehaviorLayerAware,
		behaviors:Array, 
		oldBehaviors:Array):void
	{
		var behavior:IComponentBehavior;
		var type:Class;
		
		if (oldBehaviors)
		{
			for each (behavior in oldBehaviors)
			{
				type = ToolKitUtil.getDefinition(getQualifiedClassName(behavior)) as Class;
				component.behaviorLayer.removeBehavior(type);
			}
		}
		
		if (behaviors)
		{
			for each (behavior in behaviors)
			{
				type = ToolKitUtil.getDefinition(getQualifiedClassName(behavior)) as Class;
				component.behaviorLayer.registerBehavior(type, behavior);
			}
		}
	}
	
	//--------------------------------------------------------------------------
	//
	//  Public Class [layer api] :: Methods
	//
	//--------------------------------------------------------------------------
	
	//----------------------------------
	//  model
	//----------------------------------
	
	/**
	 * Adds a model to the model aware component.
	 * 
	 * @param component An <code>IModelLayerAware</code> component to add the 
	 * model to.
	 * @param model An <code>IComponentModel</code> to add.
	 */
	public static function registerModel(
		component:IModelLayerAware, api:Class, model:IComponentModel):void
	{
		component.modelLayer.registerModel(api, model);
	}
	
	/**
	 * Removes a model from the model aware component.
	 * 
	 * @param component An <code>IModelLayerAware</code> component to add the 
	 * model to.
	 */
	public static function removeModel(
		component:IModelLayerAware, api:Class):IComponentModel
	{
		return component.modelLayer.removeModel(api);
	}
	
	//----------------------------------
	//  skin
	//----------------------------------
	
	/**
	 * Adds a skin to the skin aware component.
	 * 
	 * @param component An <code>ISkinLayerAware</code> component to add the 
	 * skin to.
	 * @param service An <code>IComponentSkin</code> to add.
	 */
//	public static function registerSkin(
//		component:ISkinLayerAware, api:Class, skin:IComponentSkin):void
//	{
//		component.skinLayer.registerSkin(api, skin);
//	}
	
	/**
	 * Removes a model from the skin aware component.
	 * 
	 * @param component An <code>ISkinLayerAware</code> component to add the 
	 * service to.
	 */
//	public static function removeSkin(
//		component:ISkinLayerAware, api:Class):IComponentSkin
//	{
//		return component.skinLayer.removeSkin(api);
//	}	
	
	//----------------------------------
	//  behavior
	//----------------------------------
	
	/**
	 * Adds a behavior to the behavior aware component.
	 * 
	 * @param component An <code>IBehaviorLayerAware</code> component to add the 
	 * behavior to.
	 * @param behavior An <code>IComponentBehavior</code> to add.
	 */
	public static function registerBehavior(
		component:IBehaviorLayerAware, api:Class, behavior:IComponentBehavior):void
	{
		component.behaviorLayer.registerBehavior(api, behavior);
	}
	
	/**
	 * Removes a behavior from the behavior aware component.
	 * 
	 * @param component An <code>IBehaviorLayerAware</code> component to add the 
	 * behavior to.
	 * @param api
	 */
	public static function removeBehavior(
		component:IBehaviorLayerAware, api:Class):IComponentBehavior
	{
		return component.behaviorLayer.removeBehavior(api);
	}	
}
}